/**
 * 
 */
package de.hft_stuttgart.dsa.ctt.validation;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import de.hft_stuttgart.dsa.ctt.model.Course;
import de.hft_stuttgart.dsa.ctt.model.CourseAssignmentMatrix;
import de.hft_stuttgart.dsa.ctt.model.Curriculum;
import de.hft_stuttgart.dsa.ctt.model.ProblemInstance;
import de.hft_stuttgart.dsa.ctt.model.Room;

/**
 * Soft Constraint Penalties are calculated here <br>
 * These are the soft constrints:<br>
 * 1. A room capacity soft constraint tries to ensure that the number of
 * students attending a lecture does not exceed the room capacity.<br>
 * 2. Lectures must be spread into a minimum number of days, penalizing
 * timetables in which lectures appear in too few distinct days.<br>
 * 3. The curricula should be compact, meaning that isolated lectures, that is
 * lectures without another adjacent lecture, should be avoided.<br>
 * 4. All lectures of a course should be held in exactly one room.<br>
 * 
 * @author Ali Yuruz
 */
public class FitnessEvaluator {
	private ProblemInstance pInstance;
	private CourseAssignmentMatrix cAssignmentMatrix;
	private final static int MIN_WORKINGDAYS_PENALTY_FACTOR = 5;
	private final static int ISOLATED_LECTURES_FACTOR = 2;

	public FitnessEvaluator(ProblemInstance problemInstance, CourseAssignmentMatrix courseAssignmentMatrix) {
		this.pInstance = problemInstance;
		this.cAssignmentMatrix = courseAssignmentMatrix;
	}

	public int calculateSoftConstraintPenalty() {
		int totalSoftConstraintCost = calculateRoomNumberOfStudentsPenalty() + calculateMinWorkingDaysPenalty() + calculateAdjacentPenalty() + calculateDifferentRoomPenalty();

		System.out.println("Total Cost (Soft) = " + totalSoftConstraintCost);
		return totalSoftConstraintCost;

	}

	/**
	 * For each Course Penalty is:<br>
	 * Rooms that are used minus one<br>
	 * frs(ci) = (nri(X) - 1)
	 * 
	 * @return
	 */
	private int calculateDifferentRoomPenalty() {
		int penalty = 0;
		Set<Integer> courseRoomSet;
		for (Course course : pInstance.getAllCourses().values()) {
			courseRoomSet = new HashSet<Integer>();
			for (Object[] coursesPerPeriod : cAssignmentMatrix.getPeriodRoomMatrix()) {

				for (int roomIndex = 0; roomIndex < coursesPerPeriod.length; roomIndex++) {
					if (course.getCourseId().equals(((Course) coursesPerPeriod[roomIndex]).getCourseId())) {
						if (!courseRoomSet.contains(roomIndex)) {
							courseRoomSet.add(roomIndex);
						}
					}
				}
			}

			// First Room is not counted as a soft constraint
			penalty = penalty + (courseRoomSet.size() - 1);

		}
		System.out.println("Cost of RoomStability (soft) :" + penalty);
		return penalty;
	}

	/**
	 * For each course Penalty is:<br>
	 *(stdk - capj), if stdk >capj; 0, otherwise
	 * 
	 * @return
	 */
	private int calculateRoomNumberOfStudentsPenalty() {
		int penalty = 0;
		List<Room> rooms = pInstance.getRooms();
		// int roomIndex = 0;
		int capacityDiff = 0;
		for (Object[] coursesPerPeriod : cAssignmentMatrix.getPeriodRoomMatrix()) {

			for (int roomIndex = 0; roomIndex < coursesPerPeriod.length; roomIndex++) {
				Course currentCourse = (Course) coursesPerPeriod[roomIndex];
				capacityDiff = (currentCourse.getNumberOfStudents() - rooms.get(roomIndex).getCapacity());
				if (capacityDiff > 0) {
					penalty = penalty + capacityDiff;
				}
			}
		}
		System.out.println("Cost of RoomCapacity (soft) : " + penalty);
		return penalty;
	}

	private int calculateAdjacentPenalty() {
		int penalty = 0;
		Object[] previousCourses;
		Course currentCourse;
		Object[] nextCourses;
		Object[][] periodRoomMatrix = cAssignmentMatrix.getPeriodRoomMatrix();
		for (int periodIndex = 0; periodIndex < periodRoomMatrix.length; periodIndex++) {
			for (Object courseObject : periodRoomMatrix[periodIndex]) {
				currentCourse = (Course) courseObject;
				for (Curriculum currentCurriculum : pInstance.getCurricula().getCurricula()) {
					if (currentCurriculum.containsCourse((currentCourse))) {
						if (isCourseAtFirstSlotOfTheDay(periodIndex)) {
							nextCourses = periodRoomMatrix[periodIndex + 1];
							if (!currentCurriculum.containsAtLeastOneOfTheseCourses(nextCourses)) {
								penalty = penalty + 1;
							}
						} else if (isCourseAtLastSlotOfTheDay(periodIndex)) {
							previousCourses = periodRoomMatrix[periodIndex - 1];
							if (!currentCurriculum.containsAtLeastOneOfTheseCourses(previousCourses)) {
								penalty = penalty + 1;
							}
						} else {
							previousCourses = periodRoomMatrix[periodIndex - 1];
							nextCourses = periodRoomMatrix[periodIndex + 1];

							if (!currentCurriculum.containsAtLeastOneOfTheseCourses(previousCourses) && !currentCurriculum.containsAtLeastOneOfTheseCourses(nextCourses)) {
								penalty = penalty + 1;
							}

						}
					}
				}
			}
		}
		penalty = penalty * ISOLATED_LECTURES_FACTOR;
		System.out.println("Cost of IsolatedLectures (soft) : " + penalty);
		return penalty;
	}

	private boolean isCourseAtLastSlotOfTheDay(int periodIndex) {
		int mod = periodIndex % pInstance.getPeriodsPerDay();

		return ((pInstance.getPeriodsPerDay() - 1) == mod);
	}

	private boolean isCourseAtFirstSlotOfTheDay(int periodIndex) {
		int mod = periodIndex % pInstance.getPeriodsPerDay();

		return (0 == mod);
	}

	/**
	 * either, (mwdi - ndi(X)), if ndi(X) <mwdi ; <br>
	 * or, 0, otherwise
	 * 
	 * @return
	 */
	private int calculateMinWorkingDaysPenalty() {
		int penalty = 0;
		int minWorkingDayOfTheCurrentCourse = 0;
		int day = 0;
		int minWorkingDayDiff = 0;

		Object[][] periodRoomMatrix = cAssignmentMatrix.getPeriodRoomMatrix();
		Set<Integer> courseMinWorkingDaySet;
		for (Course course : pInstance.getAllCourses().values()) {
			courseMinWorkingDaySet = new HashSet<Integer>();
			minWorkingDayOfTheCurrentCourse = course.getMinWorkingDays();

			for (int periodIndex = 0; periodIndex < periodRoomMatrix.length; periodIndex++) {

				for (Object courseObject : periodRoomMatrix[periodIndex]) {
					if (course.getCourseId().equals(((Course) courseObject).getCourseId())) {
						day = getDayFromPeriod(periodIndex);
						if (!courseMinWorkingDaySet.contains(day)) {
							courseMinWorkingDaySet.add(day);
						}
					}
				}

			}
			minWorkingDayDiff = minWorkingDayOfTheCurrentCourse - courseMinWorkingDaySet.size();
			if (minWorkingDayDiff > 0) {
				penalty = penalty + minWorkingDayDiff * MIN_WORKINGDAYS_PENALTY_FACTOR;
			}
		}
		System.out.println("Cost of MinWorkingDays (soft) : " + penalty);
		return penalty;
	}

	private int getDayFromPeriod(int periodIndex) {

		return periodIndex / pInstance.getPeriodsPerDay();
	}
}
